package robocup.component.coach;

import java.util.Arrays;

import robocup.component.PConf;
import robocup.component.ControllerNumbers.PLAY_MODE;
import robocup.component.geometry.Vektor;
import robocup.component.infotypes.AuralInfo;
import robocup.component.infotypes.CoachSayInfo;
import robocup.component.infotypes.CoachVisualInfo;
import robocup.component.infotypes.PlayModeInfo;
import robocup.component.infotypes.PlayerSayInfo;
import robocup.component.infotypes.PlayerTypesInfo;
import robocup.component.infotypes.RefereeInfo;
import robocup.component.infotypes.SenseBodyInfo;
import robocup.component.infotypes.VisualInfo;
import robocup.component.speechacts.coachlanguage.ActionElement;
import robocup.component.speechacts.coachlanguage.DefineDirective;
import robocup.component.speechacts.coachlanguage.DefineMessage;
import robocup.component.speechacts.coachlanguage.Directive;
import robocup.component.speechacts.coachlanguage.TypeConstants;
import robocup.component.speechacts.coachlanguage.UnumSet;

/**
 * 
 * @author Jakob Uszkoreit
 * 
 */
public class OpponentPTypeAnalyzer
    implements CoachInfoReceiver {

  // The Agent
  private CoachWorld    myWorld;

  private OLCoachTactic myTactic;

  private PConf[]       playerTypes;

  private Vektor[]      lastPlayerPositions;

  // the value we are guessing for the decay of each opponent player is not
  // noisy, however a small tolerance helps to get rid of the fp precision
  // issues ;-)
  private final double        playerDecayTolerance = 0.005;

  // private SayAction opponentTypesSay;
  private CoachSayInfo  csi                  = null;

  // after how many cycles with a single constant distribution of type guesses
  // per player do
  // we think we are sure of what we think (and ultimately communicate it to
  // the players)
  private final int           cyclesForConfidence  = 100;

  // well...
  private boolean       initialized          = false;

  // the current guess on the opponent player types
  private int[][]       decayGuessHits;

  // the number of cycles the guess distribution did not change for any given
  // player
  private int[]         constantGuessCycles;

  // the currently guessed type for each opponent
  private int[]         guessedTypes;

  // the guessed types which were broadcasted to the players last time
  private int[]         broadcastedTypes;

  // indicates wether we have reached a state of confidence for all opponents'
  // types
  private boolean       confidentForAll;

  // indicates wether confidentForAll was false at least once after the last
  // broadcast
  // of types to the players
  private boolean       lostConfidence       = true;

  public OpponentPTypeAnalyzer(
      final OLCoachTactic olCoachTactic) {

    this.myTactic = olCoachTactic;
    this.myWorld = olCoachTactic.getCwm();
  }

  public void update(final AuralInfo info) {

  }

  /**
   * Called by Perception at the arrival of a VisualInfo.
   */
  public void update(final CoachVisualInfo info) {

    // try to analyse the opponent players' heterogenous types
    this.analyzeOpponents();
  }

  public void update(final PlayerTypesInfo info) {

  }

  /**
   * This method schedules a message informing the players about their
   * opponents' types for delivery once the next freeform message can be said
   */
  public void sayOpponentTypes() {

    this.csi = new CoachSayInfo(-1);

    for (int i = 0; i < this.guessedTypes.length; i++) {
      final DefineDirective dd = new DefineDirective();
      final Directive dir = new Directive();
      final UnumSet us = new UnumSet();
      final DefineMessage dm = new DefineMessage();
      final ActionElement ac = new ActionElement();

      us.addInteger(i + 1);

      ac.setActionType(TypeConstants.ACTIONTYPE.HTYPE);
      ac.setHtypeInt(this.guessedTypes[i]);

      dm.setDefineDirective(dd);

      dd.setDirectiveName("\"OPP_PTypes\"");
      dd.setDirective(dir);

      dir.setDoIt(true);
      dir.setTeamOur(false);
      dir.setUnumSet(us);
      dir.addAction(ac);

      this.csi.addDefine(dm);

    }
    System.out.println("PLAYERTYPE : " + this.csi);
    this.myTactic.sendActions(this.csi);
    this.csi = null;

  }

  /**
   * Analyze the opponent players to narrow the set of possible of heterogeneous
   * player types for each player
   * 
   */
  public void analyzeOpponents() {

    if (this.myWorld.getPlayMode() == PLAY_MODE.PLAY_ON) {
      if (!this.initialized && this.myWorld.getPlayerTypes() != null && this.myWorld
          .getOpponentsCount() > 0) {
        // initialize our stuff before analyzing
        this.playerTypes = this.myWorld.getPlayerTypes();

        this.lastPlayerPositions = new Vektor[this.myWorld.getOpponentsCount()];

        this.decayGuessHits = new int[this.myWorld.getOpponentsCount()][];
        for (int i = 0; i < this.decayGuessHits.length; i++) {
          this.decayGuessHits[i] = new int[this.playerTypes.length];
          Arrays.fill(this.decayGuessHits[i], 0);
        }

        this.constantGuessCycles = new int[this.myWorld.getOpponentsCount()];
        Arrays.fill(this.constantGuessCycles, 0);

        this.guessedTypes = new int[this.myWorld.getOpponentsCount()];
        Arrays.fill(this.guessedTypes, 0);

        // this we do for not wasting time to tell everybody that every
        // opponent has the default type
        this.broadcastedTypes = this.guessedTypes.clone();
        this.initialized = true;
      }

      // lets be optimistic for now ;-)
      this.confidentForAll = true;

      // try to find out the decay of the opponents
      for (int opp = 0; opp < this.myWorld.getOpponentsCount(); opp++) {
        final Vektor currentPlayerPosition = this.myWorld
            .getOpponentPosition(opp + 1);

        // increase the number of constant guess cycles for this player
        // if this cycle turns out not to be constant, this will be
        // reset to 0 lateron
        if (this.constantGuessCycles == null) {
          return;
        }
        this.constantGuessCycles[opp]++;

        if (this.lastPlayerPositions[opp] != null) {

          // calculate the real decay this player had
          final Vektor positionDelta = this.myWorld.getOpponentPosition(opp + 1).sub(
              this.lastPlayerPositions[opp]);
          if (positionDelta.getLength() > 0) {
            final double actualDecay = this.myWorld.getOpponents()[opp].getSpeed()
                .getLength() / positionDelta.getLength();

            // now iterate over all possible decay values and
            // increase the counter for
            // every (semi-)equal value
            if (actualDecay > 0 && positionDelta.getLength() > 0.0001) {
              for (int type = 0; type < this.playerTypes.length; type++) {
                if (Math.abs(actualDecay - this.playerTypes[type].PLAYER_DECAY) < this.playerDecayTolerance) {

                  // increment the counter of positive guesses
                  // this type for this player
                  this.decayGuessHits[opp][type]++;

                  // if this type is not the most frequently
                  // guessed type for this player for now,
                  // look wether this type is now our best
                  // bet.
                  if (type != this.guessedTypes[opp]) {
                    if (this.decayGuessHits[opp][type] > this.decayGuessHits[opp][this.guessedTypes[opp]]) {
                      // ok, we have a new best bet
                      this.guessedTypes[opp] = type;
                      this.constantGuessCycles[opp] = 0;
                    }
                  }
                }
              }
            }
          }
        }
        this.lastPlayerPositions[opp] = currentPlayerPosition;

        // now see wether we are confident for this player at the moment
        if (this.constantGuessCycles[opp] < this.cyclesForConfidence) {
          this.confidentForAll = false;
        }

      }

      // if we are confident for all players, see wether we had lost
      // confidence at some point in time
      if (this.confidentForAll && this.lostConfidence) {
        this.lostConfidence = false;
        // compare it to the last 'confident' guesses and if they
        // differ, tell the players asap
        if (!Arrays.equals(this.broadcastedTypes, this.guessedTypes)) {
          this.broadcastedTypes = this.guessedTypes.clone();

          this.sayOpponentTypes();

          // and print the guesses to be sent
          System.out
              .println(this.myWorld.getVisualCycle() + ". Coach is broadcasting opponent player type guesses...");
          // for (int opp=0;opp<myWorld.getOpponentsCount();opp++) {
          // System.out.println("player " + (opp+1) + " has type: " +
          // guessedTypes[opp]);
          // }
        }
      }
      else if (!this.confidentForAll && !this.lostConfidence) {
        // well, we must think about resending stuff when confident
        // again
        this.lostConfidence = true;
      }

    }

  }

  /* (non-Javadoc)
   * @see robocup.component.infotypes.InfoReceiver#update(robocup.component.infotypes.SenseBodyInfo)
   */
  public void update(final SenseBodyInfo info) {

    // TODO Auto-generated method stub
    
  }

  /* (non-Javadoc)
   * @see robocup.component.infotypes.InfoReceiver#update(robocup.component.infotypes.VisualInfo)
   */
  public void update(final VisualInfo info) {

    // TODO Auto-generated method stub
    
  }

  /* (non-Javadoc)
   * @see robocup.component.infotypes.InfoReceiver#update(robocup.component.infotypes.RefereeInfo)
   */
  public void update(final RefereeInfo info) {

    // TODO Auto-generated method stub
    
  }

  /* (non-Javadoc)
   * @see robocup.component.infotypes.InfoReceiver#update(robocup.component.infotypes.PlayModeInfo)
   */
  public void update(final PlayModeInfo info) {

    // TODO Auto-generated method stub
    
  }

  /* (non-Javadoc)
   * @see robocup.component.infotypes.InfoReceiver#update(robocup.component.infotypes.PlayerSayInfo)
   */
  public void update(final PlayerSayInfo info) {

    // TODO Auto-generated method stub
    
  }

}
